/*
 * Copyright 2014-2016 CyberVision, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.spring4gwt.server;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.http.HttpServletRequest;

@SuppressWarnings("serial")
public class SpringGwtRemoteServiceServlet extends RemoteServiceServlet {


  private static final Logger LOG = LoggerFactory.getLogger(SpringGwtRemoteServiceServlet.class);

  static ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<>();

  public static HttpServletRequest getRequest() {
    return perThreadRequest.get();
  }

  public static void setRequest(HttpServletRequest request) {
    perThreadRequest.set(request);
  }

  @Override
  public void init() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Spring GWT service exporter deployed");
    }
  }

  @Override
  public String processCall(String payload) throws SerializationException {
    try {
      perThreadRequest.set(getThreadLocalRequest());
      Object handler = getBean(getThreadLocalRequest());
      RPCRequest rpcRequest = RPC.decodeRequest(payload, handler.getClass(), this);
      onAfterRequestDeserialized(rpcRequest);
      if (LOG.isDebugEnabled()) {
        LOG.debug("Invoking " + handler.getClass().getName()
            + "." + rpcRequest.getMethod().getName());
      }
      return RpcHelper
          .invokeAndEncodeResponse(
              handler,
              rpcRequest.getMethod(),
              rpcRequest.getParameters(),
              rpcRequest.getSerializationPolicy()
          );
    } catch (IncompatibleRemoteServiceException ex) {
      log("An IncompatibleRemoteServiceException was thrown while processing this call.", ex);
      return RPC.encodeResponseForFailure(null, ex);
    } catch (SerializationException ex) {
      LOG.error("An SerializationException was thrown while processing this call.", ex);
      throw ex;
    } finally {
      perThreadRequest.set(null);
    }
  }

  /**
   * Determine Spring bean to handle request based on request URL, e.g. a
   * request ending in /myService will be handled by bean with name
   * "myService".
   *
   * @param request the request
   * @return handler bean
   */
  protected Object getBean(HttpServletRequest request) {
    String service = getService(request);
    Object bean = getBean(service);
    if (!(bean instanceof RemoteService)) {
      throw new IllegalArgumentException("Spring bean is not a GWT RemoteService: "
          + service + " (" + bean + ")");
    }
    if (LOG.isDebugEnabled()) {
      LOG.debug("Bean for service " + service + " is " + bean);
    }
    return bean;
  }


  /**
   * Look up a spring bean with the specified name in the current web
   * application context.
   *
   * @param name bean name
   * @return the bean
   */
  protected Object getBean(String name) {
    WebApplicationContext applicationContext = WebApplicationContextUtils
        .getWebApplicationContext(getServletContext());
    if (applicationContext == null) {
      throw new IllegalStateException("No Spring web application context found");
    }
    if (!applicationContext.containsBean(name)) {
      {
        throw new IllegalArgumentException("Spring bean not found: " + name);
      }
    }
    return applicationContext.getBean(name);
  }

  /**
   * Parse the service name from the request URL.
   *
   * @param request the request
   * @return bean name
   */
  protected String getService(HttpServletRequest request) {
    String url = request.getRequestURI();
    String service = url.substring(url.lastIndexOf("/") + 1);
    if (LOG.isDebugEnabled()) {
      LOG.debug("Service for URL {} is {}", url, service);
    }
    return service;
  }


}
